`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: CBICR, Tsinghua Univ.
// Engineer: Hongyi Li
// 
// Create Date: 2024/12/30 11:06:54
// Design Name: 
// Module Name: Buffer Write with Arb. (For One Direction)
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////

module BufferWrArb
#(
    parameter DataWidth = 'd32,
    parameter FifoDepth = 'd4,
    parameter VCNumber  = 'd4
)(
    input                                        clk, rst_n,
    input  [DataWidth-1:0]                       i_data,
    input  [VCNumber -1:0]                       i_pop_reqs,
    input                                        i_bypass,
    output                                       o_valid,
    output [($clog2(FifoDepth) * VCNumber)-1 :0] o_credits,
    output [DataWidth-1:0]                       o_head,
    output [DataWidth-1:0]                       o_data,
    output [VCNumber -1:0]                       o_winner
);

reg                               state;
reg                               last_is_tail;
reg [VCNumber- 1:0]               last_push_reqs;
reg [DataWidth-1:0]               cur_head [0:VCNumber-1];

wire [DataWidth-1:0]              i_data_without_bypass;
wire [VCNumber-1:0]               push_reqs;
wire                              is_head;
wire                              is_tail;
wire                              switch_state;
wire [VCNumber-1:0]               is_empty; // channel-wise
wire [VCNumber-1:0]               front_is_head;

wire [VCNumber -1:0]              vc_valid;
wire [DataWidth-1:0]              vc_head  [0:VCNumber-1];
wire [DataWidth-1:0]              vc_data  [0:VCNumber-1];
wire [VCNumber -1:0]              vc_winner;

wire [VCNumber -1:0]              msk_head [0:DataWidth-1];
wire [VCNumber -1:0]              msk_data [0:DataWidth-1];


/* Buffer-Write ASM
 * 
 * State 0: Idle until Finding the Head [0] -> [1]
 * State 1: Processing until Tail       [1] -> [0]
 */

assign i_data_without_bypass = i_bypass ? 0 : i_data;
assign is_tail = (i_data_without_bypass[3:0] == 4'hf);
assign is_head = (i_data_without_bypass[3:0] == 4'h0) && (|i_data_without_bypass[(19 + VCNumber):20]);
assign switch_state = (~is_head & last_is_tail & state) | (is_head & ~is_tail & ~state);
assign push_reqs = is_head ? i_data_without_bypass[(19 + VCNumber):20] : (last_is_tail ? 0 : last_push_reqs);


// Input-Related Reg Update
always @(posedge clk) begin
    if (!rst_n) begin
        state <= 0;
        last_is_tail <= 0;
        last_push_reqs <= 0;
    end else if (switch_state) begin
        state <= ~state;
        last_is_tail <= is_tail;
        if (is_head) last_push_reqs <= i_data_without_bypass[(19 + VCNumber):20]; // ...-[]-[]-H-B-B-T-[]-...
        else last_push_reqs <= 0; // -T-[]-...
    end else begin
        state <= state;
        last_is_tail <= is_tail;
        if (is_head && state) last_push_reqs <= i_data_without_bypass[(19 + VCNumber):20]; // ...-B-T-H-B-B-...
        else last_push_reqs <= last_push_reqs; // ...-B-B- ... or ...-[]-[]-...
    end
end

// Find Local Winnner (VC -[Arbitor]-> 1)
RoundRobinArbitor #(
    .N(VCNumber)
) VCs_Arb_U (
    .clk(clk), .rst_n(rst_n),
    .i_req(vc_valid), 
    .o_grant(vc_winner)
);

// Generate Fifos
genvar i, j;
generate
    for (i = 0; i < VCNumber; i = i + 1) begin

        // One Virtual Channel
        SyncFifo #(
            .width(DataWidth),
            .depth(FifoDepth)
        ) SyncFifoU (
            .clk(clk),
            .rst_n(rst_n),
            .i_push_req(push_reqs[i]),
            .i_pop_req(i_pop_reqs[i]),
            .i_data(i_data_without_bypass),
            .o_empty(is_empty[i]),
            .o_credits(o_credits[(($clog2(FifoDepth))*(i+1))-1 : (($clog2(FifoDepth))*i)]),
            .o_data(vc_data[i])
        );

        always @(posedge clk) begin
            if (!rst_n) 
                cur_head[i] <= 0;
            else if (front_is_head[i])
                cur_head[i] <= vc_data[i];
            else
                cur_head[i] <= cur_head[i];
        end

        assign vc_valid[i] = ~is_empty[i];
        assign vc_head[i] = front_is_head[i] ? vc_data[i] : cur_head[i];
        assign front_is_head[i] = (vc_data[i][3:0] == 4'h0) && (|vc_data[i][VCNumber+19:20]);

    end

    // Mask Winner Signal
    for (j = 0; j < DataWidth; j = j + 1) begin
        for (i = 0; i < VCNumber; i = i + 1) begin
            assign msk_data[j][i] = vc_winner[i] & vc_data[i][j];
            assign msk_head[j][i] = vc_winner[i] & vc_head[i][j];
        end
        assign o_data[j] = | msk_data[j];
        assign o_head[j] = | msk_head[j];
    end
endgenerate

assign o_valid = | vc_winner;
assign o_winner =  vc_winner;

endmodule